package org.NooLab.itexx.browser;

import java.awt.BorderLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JPanel;


import org.NooLab.itexx.app.gui.definition.ITexxWindowGui;
import org.NooLab.itexx.app.gui.definition.ITexxWindowGuiFormSettings;
import org.NooLab.itexx.app.gui.definition.ITexxWindowGuiIntf;
import org.NooLab.itexx.retrieval.Page;
import org.NooLab.itexx.retrieval.PageIntf;
import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.logging.LogControl;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.pages.HtmlErrorPages;
import org.NooLab.utilities.strings.ArrUtilities;
import org.NooLab.utilities.strings.StringsUtil;

import chrriis.dj.nativeswing.swtimpl.NativeComponent;
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;
import chrriis.dj.nativeswing.swtimpl.components.WebBrowserCommandEvent;
import chrriis.dj.nativeswing.swtimpl.components.WebBrowserEvent;
import chrriis.dj.nativeswing.swtimpl.components.WebBrowserListener;
import chrriis.dj.nativeswing.swtimpl.components.WebBrowserNavigationEvent;
import chrriis.dj.nativeswing.swtimpl.components.WebBrowserWindowOpeningEvent;
import chrriis.dj.nativeswing.swtimpl.components.WebBrowserWindowWillOpenEvent;



public class DjBrowserListener extends Observable implements WebBrowserListener{

	// private JWebBrowser activeWebBrowser;
	
	private JWebBrowser webBrowser;
	DjBrowserHost browserHost;
	BrowserEventHandler eventhandler ;
	
	String statusText="" ;
	
	double relativeWaitDelayFactor = 3.0;
	long firstResponsePoint=0L, lastResponseTime = System.currentTimeMillis()+500 ;
	long lastPartialCompletionTime = System.currentTimeMillis();
	double averageResponseTime =-1.0, timeBufferOk=-999.0;
	
			
	ArrayList<Long> statusTimes = new ArrayList<Long> (); 
	boolean approximatelyCompleted = false;
	boolean imagesExpected=false, htmlClosingDetected =false;
	boolean completionDetectorIsDisabled = false;
	
	int waitingForCompletion = 0 ;
	
	String browserOutput="";
	String currentCollectedContentStr="" ;
	ArrayList<String> collectedOutput = new ArrayList<String> ();
	
	int zz=0;
	int outlevel=3;
	
	UpstreamInformation upstreamInfo;
	
	private CompletionDetector completionDetector;
	boolean cdectIsRunning=false, informingUpstream=false;

	private String contextUrl="";
	private PageIntf page;
	private String lastLink="", lastFile ="";
	private String contextStr="";
	public boolean ajaxPage=false;
	private boolean isAjaxChecking, isBodyChecking;
	
	String internalClient="";
	
	DFutils fileutil = new DFutils();
	StringsUtil strgutil = new StringsUtil();
	PrintLog out = new PrintLog(2,true) ;

	// ========================================================================
	public DjBrowserListener (DjBrowserHost browserhost){
		
		browserHost = browserhost;
		LogControl.Level = 2 ;
		completionDetector = new CompletionDetector(this);
		upstreamInfo = new UpstreamInformation(); 
	}
	// ========================================================================
	
	public void addObserver(Observer obs){
		if (obs!=null){
			super.addObserver(obs) ;
		}
	}
	
	
	// ------------------------------------------------------------------------

	public void close(){
		completionDetectorIsDisabled = true;
		completionDetector.stop();
		eventhandler.close() ;
	}

	public void reset(){
		
		statusTimes.clear();
		averageResponseTime = -1L;
		lastPartialCompletionTime = System.currentTimeMillis();
		completionDetectorIsDisabled = false;
		// this.set-Context-Str(""); should be done in an extra step !
		// webBrowser = null;
		collectedOutput.clear();
		
		out.print(3, "\nBrowserListener has been reset!\n") ;
	}
	
	public void disable() {
		
		completionDetectorIsDisabled = true;
	}

	
	public void setContextStr(String context) {
		// 
		if (context.length()==0){
			int k;
			k=0;
		}
		contextStr = context;
		out.print(3, "Listener: context string set to : "+context) ;
	}
	
	public String getContextStr() {
		return contextStr;
	}

	@Override
	public void commandReceived(WebBrowserCommandEvent arg0) {
		// 
		
		out.print(outlevel, "commandReceived");
	}

	@Override
	public void loadingProgressChanged(WebBrowserEvent arg0) {
		//
		
		if (firstResponsePoint==0){
			firstResponsePoint = System.currentTimeMillis() ;
		}
		
		lastPartialCompletionTime = System.currentTimeMillis();
		out.print(outlevel, " ... setting lastPartialCompletionTime ...") ;
		
		String str = arg0.getWebBrowser().getHTMLContent();
		
		if (str==null){
			return;
		}
		if ((contextUrl!=null) && (contextUrl.toLowerCase().contains(".google."))){
			
			String tmpFile = fileutil.gettempfile( "~itx-listener-0a"+zz+".html") ;
			fileutil.writeFileSimple( tmpFile, str) ;
			lastFile = tmpFile;
		}
		
		checkAjaxStatus(str); 
		// collectedOutput.add(str);
		
		zz++;
		
		 
		// out.print(2, str+"\n");
		browserOutput = str;
		if (str.toLowerCase().contains("</html>")){
			// out.print(2,browserOutput );
			htmlClosingDetected = true;
			
			// tmpFile = "D:/temp/googlehtml-0x"+zz+".txt";
			// (new DFutils()).writeFileSimple( tmpFile, str) ;
			zz++;
		}

		if (str!=null){
			boolean hb = str.indexOf("<img")>0;
			if (hb){
				imagesExpected = true;
				out.print(outlevel,"\n\nimage(s) expected...\n");
			}
			 
			String endofstr = "";
			if (str.length()>1000){
				endofstr = str.substring(str.length()-300,str.length());
			}
			out.print(outlevel,"\n\nLength of received part : "+str.length());
			out.print(outlevel,"\n"+ str.substring(0, Math.min(5000,str.length()))+" ... ... ... " +
					     		 ""+ endofstr );
			
			
			
			currentCollectedContentStr = str;
		}
	}

	
	
	protected void checkAjaxStatus(String str) {
		
		// testing for lots of scripts... 
		if (str==null){
			return;
		}
		str = str.toLowerCase() ;
		
		if (isAjaxChecking){
			out.print(2, "already checking for ajax...") ;
			return;
		}
		
		isAjaxChecking=true;
		int s=0, total;
		int p2=0,p1=0;
		total = str.length() ;
		int z=0,zz=0;
		
		while ((p1>=0) && (zz<100) ){
			
			p1 = str.indexOf("<script",p2);
			if (p1>=0){
				p2 = str.indexOf("</script",p1);
				
				if (p2>p1){
					z++;
					s = s + (p2-p1);
				} // p2 ?
			} // p1 ?
			zz++;
		} // -> ?
		
		if ((s>20000) || (z>50) || ( ((double)s/(double)total)>0.3)){
			ajaxPage = true;
		}
		isAjaxChecking = false;
	}
	
	
	
	protected int getBodySize(String str) {
		
		int bodySize = 0;
		
		str = str.toLowerCase() ;
		
		if (isBodyChecking){
			out.print(outlevel, "already checking for body size...") ;
			return -2;
		}
		
		isBodyChecking=true;
		int total;
		int p2=0,p1=0;
		total = str.length() ;
		int z=0,zz=0;
		
		while ((p1>=0) && (zz<100) ){
			
			p1 = str.indexOf("<body",p2);
			if (p1>=0){
				p2 = str.indexOf("</body",p1);
				
				if (p2>p1){
					z++;
					bodySize = bodySize + (p2-p1);
				} // p2 ?
			} // p1 ?
			zz++;
		} // -> ?
		

		isBodyChecking = false;

		return bodySize ;
	}

	
	@Override
	public void locationChangeCanceled(WebBrowserNavigationEvent arg0) {
		// 
		out.print(outlevel, "LCC");
	}

	@Override
	public void locationChanged(WebBrowserNavigationEvent arg0) {
		// 
		out.printErr(outlevel, "\n>>> LocChg --> "+ arg0 );
		// arg0.getWebBrowser().get
	}

	@Override
	public void locationChanging(WebBrowserNavigationEvent arg0) {
		// 
		out.print(outlevel, "LocChhhggg");
		if (firstResponsePoint==0){
			firstResponsePoint = System.currentTimeMillis() ;
			lastPartialCompletionTime = System.currentTimeMillis() ;
			out.print(outlevel, " ... setting lastPartialCompletionTime ...") ;

		}

	}

	@Override
	public void statusChanged(WebBrowserEvent arg0) {
		// 
		
		
		statusText = arg0.getWebBrowser().getStatusText();
		
		if (browserHost.app.getAppCommons().isApplicationIsStopping()){
			
			return;
		}
		
		if (statusText.toLowerCase().contains("done")){
		
			statusTimes.add(System.currentTimeMillis() - lastPartialCompletionTime ); //firstResponsePoint);
			
			if ((statusTimes.size()>=1) && (statusTimes.size()<=2) && (cdectIsRunning==false) ){
				
				out.print(outlevel,"\nCompletion Detector has been started...");
				completionDetector.start();
				out.delay(5);
			}
			averageResponseTime = getAverageResponseTimeCalc();
			
			
			int t=statusTimes.size() ;
			if (t>=2){
				lastResponseTime = statusTimes.get(t-1) - statusTimes.get(t-2);
			}
			out.delay(2);
			out.printErr(outlevel, "\naverageResponseTime : "+ String.format("%.3f", averageResponseTime));
			out.printErr(outlevel,   "timeBufferOk        : "+ String.format("%.3f", timeBufferOk));

			if (averageResponseTime>6000){
				// and all values are smaller than that before
				long lt = statusTimes.get(statusTimes.size()-1);
				statusTimes.clear();	
				statusTimes.add(lt) ;
			}
			
		}
		
		averageResponseTime = getAverageResponseTimeCalc();
		lastPartialCompletionTime = System.currentTimeMillis();
		out.print(outlevel, " ... setting lastPartialCompletionTime ...") ;

		
		if ((imagesExpected) && (waitingForCompletion ==0) && (statusText.toLowerCase().contains( "downloading picture" ))){
			waitingForCompletion = 1 ;
		}else{
			if (waitingForCompletion >=100){
				if (statusText.toLowerCase().contains( "done" )){
					if (htmlClosingDetected){
						approximatelyCompleted = true;
						timeBufferOk= 0.0;
						
					}
				}
			}
		}
		
		/*
		 * the listener also responds to mouse-over-<a href="*">!!
		 * so, is it a link ?
		 */
		String str = statusText.trim();
		int p=str.indexOf(" "); // no blanks within the string in case of url!
		
		boolean hb = (p<0) && (strgutil.isUrl(str)); 
		
		if (hb){
			out.printErr(outlevel, "link = " + statusText );
			lastLink = statusText;
		}else{
			out.printErr(outlevel, "...statusChanged : "+statusText );
		}
		

		// timeBufferOk is calculated in a supervisor thread...
		if ((approximatelyCompleted) && (timeBufferOk>150)){

			out.printErr(outlevel, "\naverageResponseTime : "+ String.format("%.3f", averageResponseTime));
			out.print(outlevel,     "last time measure (2): "+String.format("%.1f", timeBufferOk));

			// setChanged();
			// this.notifyObservers( );
		}
		
		// upstreamInfo.inform(currentCollectedContentStr);
	}

	private double getAverageResponseTimeCalc() {
		double aRT = 0.0;
		
		if (statusTimes.size()>0){
			aRT = ((double)(1.0*ArrUtilities.arraySumL(statusTimes)))/((double)statusTimes.size()) ;
		}
		aRT = Math.max(200,aRT );
		
		return aRT;
	}
	
	@Override
	public void titleChanged(WebBrowserEvent arg0) {
		// 
		out.print(outlevel, "title changed...");
	}

	@Override
	public void windowClosing(WebBrowserEvent arg0) {
		// 
		out.printErr(outlevel, "windowClosing : "+arg0.getWebBrowser().getStatusText());
	}

	@Override
	public void windowOpening(WebBrowserWindowOpeningEvent arg0) {
		// 
		out.print(outlevel, "win open");
	}

	@Override
	public void windowWillOpen(WebBrowserWindowWillOpenEvent arg0) {
		// 
		out.print(outlevel, "wwo");
	}


	public void setPage(PageIntf page) {
		// 
		this.page = page;
	}

	public String getStatusText() {
		return statusText;
	}

	public String getLastLink() {
		return lastLink;
	}

	public String getInternalClient() {
		return internalClient;
	}

	public void setInternalClient(String internalClient) {
		this.internalClient = internalClient;
	}

	public void setActiveWebBrowser(JWebBrowser webBrowser) {
		this.webBrowser = webBrowser;
	}

	public void setContextUrl( String url){
		contextUrl = url;
	}

	public boolean isImagesExpected() {
		return imagesExpected;
	}

	public int getWaitingForCompletion() {
		return waitingForCompletion;
	}

	public String getBrowserOutput() {
		return browserOutput;
	}

	
	public boolean isApproximatelyCompleted() {
		return approximatelyCompleted;
	}
	


	class UpstreamInformation{
		
		public UpstreamInformation(){
			
		}
		
		public void inform(String currentCollectedContentStr){
			
			if (informingUpstream==false){
				return;
			}
			if (contextStr.length()==0){
				int k;
				k=0;
				setChanged();
				notifyObservers( "" );
				return;
			}
			if (page!=null){
				page.setHTML(currentCollectedContentStr);
				setChanged();
				notifyObservers( page) ;// currentCollectedContentStr);
			}else{
				setChanged();
				notifyObservers( currentCollectedContentStr) ;
			}
			// update() is DjBrowserHost()
		}
		
	}
	
	public void stopCompletionDetector() {
		
		out.print(outlevel, "Listener: attempting to stop completion detector...");
		
		cdectIsRunning = false;
		informingUpstream=false;
		out.delay(130) ;
	}



	class CompletionDetector implements Runnable{

		Thread cdThrd;
		DjBrowserListener listener;
		long ajaxWait=0;
		long ajaxStart;
		
		
		public CompletionDetector(DjBrowserListener listener) {
			// 
			this.listener = listener;
		}
		public void start(){
			if (cdectIsRunning == false) {
				cdThrd = new Thread(this, "cdThrd");
				informingUpstream=true;
				cdThrd.start();
			}
		}
		public void stop(){
			cdectIsRunning=false;
			out.delay(22) ;
		}
		
		private boolean contentIndicatesCompletion(String htmlStr){
			boolean rB=false;
			
			htmlStr = htmlStr.toLowerCase();
			
			int len = htmlStr.length();
			if (len>12000){
				int k;
				k=0;
			}
			
			if (htmlStr.toLowerCase().contains("<body")) {
				if (htmlStr.length()>200){
					rB=true;
				}
				if (rB==false){
					rB = HtmlErrorPages.htmlPageIsErrorPage( htmlStr) ;
				}
			}else{
				// rB = len>20000;
			}
			if (rB==false){
				// in case of firefox ErrorPageTemplate.css
				rB = htmlStr.contains("errorpagetemplate.css"); // FIXME: this must be taken from a config file
			}
			if (rB==false){
				// in case of firefox ErrorPageTemplate.css
				rB = htmlStr.contains("errorpagestrings.js");
			}
			if (rB==false){
				rB = htmlStr.contains("the domain name server (dns) is not reachable");
				if (rB){
					// applies for firefox and iex
					rB = htmlStr.contains("<!-- error title -->");
				}
			}
			
			return rB;
		}
		
		@Override
		public void run() {
			
			boolean informed=false;
			
			if (completionDetectorIsDisabled){
				// this happens if it is navigated in the startup, or directly
				return;
			}
			cdectIsRunning=true;
			double v; int d=20;
			while (cdectIsRunning){

				v  = (1.0*System.currentTimeMillis() - 
					 (lastPartialCompletionTime + (relativeWaitDelayFactor * averageResponseTime)) ) ;
				timeBufferOk = v ;
				out.delay(d) ;
				
				// out.print(outlevel, "current time measure : "+String.format("%.1f", timeBufferOk));
				// what about large documents like pdf ? should we put an expected maximum ?
				// pdf documents will be downloaded on a different route anyway
				if (cdectIsRunning==false){
					break;
				}
				if ((timeBufferOk>7500) || 
					(((timeBufferOk>150) && (timeBufferOk<10000000) && (contentIndicatesCompletion(currentCollectedContentStr))))){


					String _currentCollectedContentStr = currentCollectedContentStr;
					if ((ajaxWait<25) || (ajaxWait>2200)){
						out.printErr(outlevel, "\naverageResponseTime : "+ String.format("%.3f", averageResponseTime));
						out.print(outlevel,     "last time measure (2): "+ String.format("%.1f", timeBufferOk));
						out.print(outlevel,     "ajax waiting...      : "+ ajaxWait);
					}
					// String tmpFile = "D:/temp/googlehtml-a.html";
					// (new DFutils()).writeFileSimple( tmpFile, _currentCollectedContentStr) ;

					if (ajaxWait>3000){
						if (informed==false){
							int cL =0;
							
							if (fileutil.fileexists(lastFile)){
								
								try {
									
									_currentCollectedContentStr = (new DFutils()).readFile2String(listener.lastFile);
									cL = _currentCollectedContentStr.length();
									
								} catch (IOException e) {
									e.printStackTrace();
								}
							}

							// this will execute delayed, thus we have to wait.. .webBrowser != null ???
							BrowserWorker swingWorker = new BrowserWorker( browserHost,webBrowser );
							swingWorker.execute();
							
							String str = ""; int zs=0;
							while ((str.length()==0) && (zs<40)){
								str = swingWorker.getHTMLContent();
								zs++;
								out.delay(100);
							}
								
							int sL = str.length();
							
							if (sL>cL){
								_currentCollectedContentStr=str;
							}

							informed=true;
							upstreamInfo.inform(_currentCollectedContentStr);
						}
						break;
					}

					if (cdectIsRunning ){  
						
						cdectIsRunning=false;
						d = 500;
						listener.isAjaxChecking = false;
						if ((currentCollectedContentStr.length()>1000) &&(listener.ajaxPage==false)){
							checkAjaxStatus(currentCollectedContentStr);
						}
						if (listener.ajaxPage){
							if (ajaxWait<=1){
								out.print(outlevel, "page is presumably ajaxed...");
								ajaxStart= System.currentTimeMillis()-5 ;
							}
							ajaxWait = System.currentTimeMillis() - ajaxStart;
							
							if (ajaxWait<1200){
								out.delay(50);
								cdectIsRunning=true;
								out.print(outlevel,     "continue waiting...");
								continue;
							}
						}

						
						// if there is masses of javascript, we might conclude that there is lots of ajax...
						// thus we have to wait longer than normal
						if ((contextUrl!=null) && (contextUrl.toLowerCase().contains(".google."))){
								
								if ((listener.getBodySize(currentCollectedContentStr)>5000)&&
									(currentCollectedContentStr.length()>30000) && 
									(currentCollectedContentStr.toLowerCase().indexOf("<!--a-->")<0)){
									
									cdectIsRunning=true;
									continue;
								}
								
								try {
									out.delay(10);
									out.print(outlevel,"last buffer file (1): "+ lastFile);
									if (fileutil.fileexists(lastFile)){
										_currentCollectedContentStr = (new DFutils()).readFile2String(listener.lastFile);
									}
									out.print(outlevel,"last buffer file (2): "+ lastFile);
								} catch (IOException e) {
									e.printStackTrace();
								}
								
								if (page!=null){
									// String tmpFile = "D:/temp/googlehtml-0cc.html";
									// (new DFutils()).writeFileSimple( tmpFile, _currentCollectedContentStr) ;
									
									page.setKnownBufferFile( lastFile );
									page.setHTML( _currentCollectedContentStr );
									
								}
								
							
						}// ? google ?
						
						if (_currentCollectedContentStr.length()>0){
							informed=true;
							upstreamInfo.inform(_currentCollectedContentStr);
						}
						
						d = 20;
					}else{
						if (informed==false){
							if (_currentCollectedContentStr.length()>0){
								upstreamInfo.inform(_currentCollectedContentStr);
								informed=true;
								break;
							}
						}
					}
					/*
					 * setChanged();
					 * notifyObservers(currentCollectedContentStr);
					 */
					
					cdectIsRunning=false;
				}					
				
				

			}
			cdectIsRunning=false;
			out.print(outlevel, "Listener: completion detector stopped (cdectIsRunning)...");
		}
		
		
	}

	// called from DjBrowserHost::prepare(), i.e. VERY early
	public void establishWebBrowserWindow(ITexxWindowGuiIntf windowgui, JWebBrowser webBrowser) {
		// 
		ITexxWindowGuiFormSettings guiFormSettings ;
		
		
		guiFormSettings = windowgui.getGuiFormSettings() ;
		JPanel jPanelWebBrowser ;
		
		this.webBrowser = webBrowser;
		jPanelWebBrowser = windowgui.getPanelMetaSearchBrowser(); 
		
		jPanelWebBrowser.add(webBrowser, BorderLayout.CENTER);
		
		webBrowser.setVisible(true);
		
		eventhandler = new BrowserEventHandler(this, webBrowser); 
		eventhandler.define();
	}
	
}


